Skip to content

Commit

Permalink
Merge pull request #165 from tig/tznind-gradients
Browse files Browse the repository at this point in the history
Added Border Settings & BorderSettings.Gradient
  • Loading branch information
tznind authored Jul 9, 2024
2 parents fef6f33 + f770bb9 commit 097a800
Show file tree
Hide file tree
Showing 20 changed files with 610 additions and 507 deletions.
23 changes: 9 additions & 14 deletions Terminal.Gui/Drawing/FillPair.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@

namespace Terminal.Gui;


/// <summary>
/// Describes a pair of <see cref="IFill"/> which cooperate in creating
/// <see cref="Attribute"/>. One gives foreground color while other gives background.
/// Describes a pair of <see cref="IFill"/> which cooperate in creating
/// <see cref="Attribute"/>. One gives foreground color while other gives background.
/// </summary>
public class FillPair
{
/// <summary>
/// Creates a new instance using the provided fills for foreground and background
/// color when assembling <see cref="Attribute"/>.
/// Creates a new instance using the provided fills for foreground and background
/// color when assembling <see cref="Attribute"/>.
/// </summary>
/// <param name="fore"></param>
/// <param name="back"></param>
Expand All @@ -21,26 +19,23 @@ public FillPair (IFill fore, IFill back)
}

/// <summary>
/// The fill which provides point based foreground color.
/// The fill which provides point based foreground color.
/// </summary>
public IFill Foreground { get; init; }

/// <summary>
/// The fill which provides point based background color.
/// The fill which provides point based background color.
/// </summary>
public IFill Background { get; init; }

/// <summary>
/// Returns the color pair (foreground+background) to use when rendering
/// a rune at the given <paramref name="point"/>.
/// Returns the color pair (foreground+background) to use when rendering
/// a rune at the given <paramref name="point"/>.
/// </summary>
/// <param name="point"></param>
/// <returns></returns>
public Attribute GetAttribute (Point point)
{
return new Attribute (
Foreground.GetColor (point),
Background.GetColor (point)
);
return new (Foreground.GetColor (point), Background.GetColor (point));
}
}
138 changes: 74 additions & 64 deletions Terminal.Gui/Drawing/Gradient.cs
Original file line number Diff line number Diff line change
@@ -1,34 +1,30 @@
// This code is a C# port from python library Terminal Text Effects https://github.com/ChrisBuilds/terminaltexteffects/
// This code is a C# port from python library Terminal Text Effects https://github.com/ChrisBuilds/terminaltexteffects/

namespace Terminal.Gui;

using System;
using System.Collections.Generic;
using System.Linq;

/// <summary>
/// Describes the pattern that a <see cref="Gradient"/> results in e.g. <see cref="Vertical"/>, <see cref="Horizontal"/> etc
/// Describes the pattern that a <see cref="Gradient"/> results in e.g. <see cref="Vertical"/>,
/// <see cref="Horizontal"/> etc
/// </summary>
public enum GradientDirection
{
/// <summary>
/// Color varies along Y axis but is constant on X axis.
/// Color varies along Y axis but is constant on X axis.
/// </summary>
Vertical,

/// <summary>
/// Color varies along X axis but is constant on Y axis.
/// Color varies along X axis but is constant on Y axis.
/// </summary>
Horizontal,


/// <summary>
/// Color varies by distance from center (i.e. in circular ripples)
/// Color varies by distance from center (i.e. in circular ripples)
/// </summary>
Radial,

/// <summary>
/// Color varies by X and Y axis (i.e. a slanted gradient)
/// Color varies by X and Y axis (i.e. a slanted gradient)
/// </summary>
Diagonal
}
Expand All @@ -41,22 +37,24 @@ public enum GradientDirection
public class Gradient
{
/// <summary>
/// The discrete colors that will make up the <see cref="Gradient"/>.
/// The discrete colors that will make up the <see cref="Gradient"/>.
/// </summary>
public List<Color> Spectrum { get; private set; }
public List<Color> Spectrum { get; }

private readonly bool _loop;
private readonly List<Color> _stops;
private readonly List<int> _steps;


/// <summary>
/// Creates a new instance of the <see cref="Gradient"/> class which hosts a <see cref="Spectrum"/>
/// of colors including all <paramref name="stops"/> and <paramref name="steps"/> interpolated colors
/// between each corresponding pair.
/// Creates a new instance of the <see cref="Gradient"/> class which hosts a <see cref="Spectrum"/>
/// of colors including all <paramref name="stops"/> and <paramref name="steps"/> interpolated colors
/// between each corresponding pair.
/// </summary>
/// <param name="stops">The colors to use in the spectrum (N)</param>
/// <param name="steps">The number of colors to generate between each pair (must be N-1 numbers).
/// If only one step is passed then it is assumed to be the same distance for all pairs.</param>
/// <param name="steps">
/// The number of colors to generate between each pair (must be N-1 numbers).
/// If only one step is passed then it is assumed to be the same distance for all pairs.
/// </param>
/// <param name="loop">True to duplicate the first stop and step so that the gradient repeats itself</param>
/// <exception cref="ArgumentException"></exception>
public Gradient (IEnumerable<Color> stops, IEnumerable<int> steps, bool loop = false)
Expand All @@ -73,13 +71,13 @@ public Gradient (IEnumerable<Color> stops, IEnumerable<int> steps, bool loop = f
// If multiple colors and only 1 step assume same distance applies to all steps
if (_stops.Count > 2 && _steps.Count == 1)
{
_steps = Enumerable.Repeat (_steps.Single (),_stops.Count() - 1).ToList();
_steps = Enumerable.Repeat (_steps.Single (), _stops.Count () - 1).ToList ();
}

if (_steps.Any (step => step < 1))
{
throw new ArgumentException ("Steps must be greater than 0.");
}
}

if (_steps.Count != _stops.Count - 1)
{
Expand All @@ -91,11 +89,13 @@ public Gradient (IEnumerable<Color> stops, IEnumerable<int> steps, bool loop = f
}

/// <summary>
/// Returns the color to use at the given part of the spectrum
/// Returns the color to use at the given part of the spectrum
/// </summary>
/// <param name="fraction">Proportion of the way through the spectrum, must be between
/// 0 and 1 (inclusive). Returns the last color if <paramref name="fraction"/> is
/// <see cref="double.NaN"/>.</param>
/// <param name="fraction">
/// Proportion of the way through the spectrum, must be between
/// 0 and 1 (inclusive). Returns the last color if <paramref name="fraction"/> is
/// <see cref="double.NaN"/>.
/// </param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public Color GetColorAtFraction (double fraction)
Expand All @@ -105,30 +105,32 @@ public Color GetColorAtFraction (double fraction)
return Spectrum.Last ();
}

if (fraction < 0 || fraction > 1)
if (fraction is < 0 or > 1)
{
throw new ArgumentOutOfRangeException (nameof (fraction), "Fraction must be between 0 and 1.");
throw new ArgumentOutOfRangeException (nameof (fraction), @"Fraction must be between 0 and 1.");
}

int index = (int)(fraction * (Spectrum.Count - 1));
var index = (int)(fraction * (Spectrum.Count - 1));

return Spectrum [index];
}

private List<Color> GenerateGradient (IEnumerable<int> steps)
{
List<Color> gradient = new List<Color> ();
List<Color> gradient = new ();

if (_stops.Count == 1)
{
for (int i = 0; i < steps.Sum (); i++)
for (var i = 0; i < steps.Sum (); i++)
{
gradient.Add (_stops [0]);
}

return gradient;
}

var stopsToUse = _stops.ToList ();
var stepsToUse = _steps.ToList ();
List<Color> stopsToUse = _stops.ToList ();
List<int> stepsToUse = _steps.ToList ();

if (_loop)
{
Expand All @@ -137,109 +139,117 @@ private List<Color> GenerateGradient (IEnumerable<int> steps)
}

var colorPairs = stopsToUse.Zip (stopsToUse.Skip (1), (start, end) => new { start, end });
var stepsList = stepsToUse;
List<int> stepsList = stepsToUse;

foreach (var (colorPair, thesteps) in colorPairs.Zip (stepsList, (pair, step) => (pair, step)))
foreach ((var colorPair, int thesteps) in colorPairs.Zip (stepsList, (pair, step) => (pair, step)))
{
gradient.AddRange (InterpolateColors (colorPair.start, colorPair.end, thesteps));
}

return gradient;
}

private IEnumerable<Color> InterpolateColors (Color start, Color end, int steps)
private static IEnumerable<Color> InterpolateColors (Color start, Color end, int steps)
{
for (int step = 0; step < steps; step++)
for (var step = 0; step < steps; step++)
{
double fraction = (double)step / steps;
int r = (int)(start.R + fraction * (end.R - start.R));
int g = (int)(start.G + fraction * (end.G - start.G));
int b = (int)(start.B + fraction * (end.B - start.B));
yield return new Color (r, g, b);
var r = (int)(start.R + fraction * (end.R - start.R));
var g = (int)(start.G + fraction * (end.G - start.G));
var b = (int)(start.B + fraction * (end.B - start.B));

yield return new (r, g, b);
}

yield return end; // Ensure the last color is included
}


/// <summary>
/// <para>
/// Creates a mapping starting at 0,0 and going to <paramref name="maxRow"/> and <paramref name="maxColumn"/>
/// (inclusively) using the supplied <paramref name="direction"/>.
/// </para>
/// <para>
/// Note that this method is inclusive i.e. passing 1/1 results in 4 mapped coordinates.
/// </para>
/// <para>
/// Creates a mapping starting at 0,0 and going to <paramref name="maxRow"/> and <paramref name="maxColumn"/>
/// (inclusively) using the supplied <paramref name="direction"/>.
/// </para>
/// <para>
/// Note that this method is inclusive i.e. passing 1/1 results in 4 mapped coordinates.
/// </para>
/// </summary>
/// <param name="maxRow"></param>
/// <param name="maxColumn"></param>
/// <param name="direction"></param>
/// <returns></returns>
public Dictionary<Point, Color> BuildCoordinateColorMapping (int maxRow, int maxColumn, GradientDirection direction)
{
var gradientMapping = new Dictionary<Point, Color> ();
Dictionary<Point, Color> gradientMapping = new ();

switch (direction)
{
case GradientDirection.Vertical:
for (int row = 0; row <= maxRow; row++)
for (var row = 0; row <= maxRow; row++)
{
double fraction = maxRow == 0 ? 1.0 : (double)row / maxRow;
Color color = GetColorAtFraction (fraction);
for (int col = 0; col <= maxColumn; col++)

for (var col = 0; col <= maxColumn; col++)
{
gradientMapping [new Point (col, row)] = color;
gradientMapping [new (col, row)] = color;
}
}

break;

case GradientDirection.Horizontal:
for (int col = 0; col <= maxColumn; col++)
for (var col = 0; col <= maxColumn; col++)
{
double fraction = maxColumn == 0 ? 1.0 : (double)col / maxColumn;
Color color = GetColorAtFraction (fraction);
for (int row = 0; row <= maxRow; row++)

for (var row = 0; row <= maxRow; row++)
{
gradientMapping [new Point (col, row)] = color;
gradientMapping [new (col, row)] = color;
}
}

break;

case GradientDirection.Radial:
for (int row = 0; row <= maxRow; row++)
for (var row = 0; row <= maxRow; row++)
{
for (int col = 0; col <= maxColumn; col++)
for (var col = 0; col <= maxColumn; col++)
{
double distanceFromCenter = FindNormalizedDistanceFromCenter (maxRow, maxColumn, new Point (col, row));
double distanceFromCenter = FindNormalizedDistanceFromCenter (maxRow, maxColumn, new (col, row));
Color color = GetColorAtFraction (distanceFromCenter);
gradientMapping [new Point (col, row)] = color;
gradientMapping [new (col, row)] = color;
}
}

break;

case GradientDirection.Diagonal:
for (int row = 0; row <= maxRow; row++)
for (var row = 0; row <= maxRow; row++)
{
for (int col = 0; col <= maxColumn; col++)
for (var col = 0; col <= maxColumn; col++)
{
double fraction = ((double)row * 2 + col) / (maxRow * 2 + maxColumn);
Color color = GetColorAtFraction (fraction);
gradientMapping [new Point (col, row)] = color;
gradientMapping [new (col, row)] = color;
}
}

break;
}

return gradientMapping;
}

private double FindNormalizedDistanceFromCenter (int maxRow, int maxColumn, Point coord)
private static double FindNormalizedDistanceFromCenter (int maxRow, int maxColumn, Point coord)
{
double centerX = maxColumn / 2.0;
double centerY = maxRow / 2.0;
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;
}
}
}
Loading

0 comments on commit 097a800

Please sign in to comment.