-
Notifications
You must be signed in to change notification settings - Fork 725
Gradients - From Terminal Text Effects #3586
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 32 commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
f312022
Start pulling Animation a bit at a time through chat gpt from https:/…
tznind 7d62ad2
More random code ported
tznind 8fbf4d5
Building
tznind b82eda4
Rainbow gradient!
tznind 5cac659
Investigate adding a bouncing ball animation
tznind d7a4e0e
Ball bouncing properly
tznind ab07f53
Radial gradient
tznind 9672de7
Add other gradients
tznind 1f13ec5
Streamline gradients example and add tabs
tznind 3171df8
LineCanvas support for gradient fill
tznind c1e82e6
Add tests, corners not working properly for some reason
tznind a5c1d73
Fix bug in StraightLine as it calculates intersections
tznind e80f61b
Restore diagonal demo
tznind cbcf4b5
Remove everything except gradient
tznind f7d584b
Add attribution
tznind 5a28eb9
Merge branch 'v2_develop' into gradients
tznind be764b7
xml doc
tznind a167366
Tests, xmldoc and guards
tznind 116cba8
Gradient tests
tznind 5a548f0
Merge branch 'v2_develop' into gradients
tznind 0631579
Fix naming and tests compiler warnings
tznind 1ef5645
Merge branch 'gradients' of https://github.com/tznind/gui.cs into gra…
tznind 5240122
Merge branch 'v2_develop' into gradients
tznind 8a56586
Fix note comment and add tests for SolidFill class
tznind afc0bf0
Fix dodgy constructor on FillPair and add tests
tznind d15f3af
Add tests that confirm LineCanvas behavior with Fill
tznind c62cd84
Add xml comment
tznind b9a8c7d
Fix GradientFill when not at origin
tznind 18e1956
Make gradients flow like a flow layout
tznind 04ca5fd
Merge branch 'v2_develop' into gradients
tznind 2544d07
Merge branch 'v2_develop' into gradients
tznind ddaec2e
Merge branch 'v2_develop' into gradients
tig c981eef
Fix hanging xml comment
tznind 1ba84de
Fix bad namespace
tznind fef6f33
Merge branch 'v2_develop' into gradients
tig 65efddb
Added Border Settings.
tig d0f1280
Code cleanup
tig e09c0e6
Merge branch 'gradients' into tznind-gradients
tig f770bb9
Code cleanup2
tig 097a800
Merge pull request #165 from tig/tznind-gradients
tznind File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
| ||
using Terminal.Gui.Drawing; | ||
|
||
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. | ||
/// </summary> | ||
public class FillPair | ||
{ | ||
/// <summary> | ||
/// 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> | ||
public FillPair (IFill fore, IFill back) | ||
{ | ||
Foreground = fore; | ||
Background = back; | ||
} | ||
|
||
/// <summary> | ||
/// The fill which provides point based foreground color. | ||
/// </summary> | ||
public IFill Foreground { get; init; } | ||
|
||
/// <summary> | ||
/// 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"/>. | ||
/// </summary> | ||
/// <param name="point"></param> | ||
/// <returns></returns> | ||
public Attribute GetAttribute (Point point) | ||
{ | ||
return new Attribute ( | ||
Foreground.GetColor (point), | ||
Background.GetColor (point) | ||
); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
// 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 | ||
/// </summary> | ||
public enum GradientDirection | ||
{ | ||
/// <summary> | ||
/// 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. | ||
/// </summary> | ||
Horizontal, | ||
|
||
|
||
/// <summary> | ||
/// 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) | ||
/// </summary> | ||
Diagonal | ||
} | ||
|
||
/// <summary> | ||
/// Describes | ||
tig marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
/// </summary> | ||
public class Gradient | ||
{ | ||
/// <summary> | ||
/// The discrete colors that will make up the <see cref="Gradient"/>. | ||
/// </summary> | ||
public List<Color> Spectrum { get; private set; } | ||
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. | ||
/// </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="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) | ||
{ | ||
_stops = stops.ToList (); | ||
|
||
if (_stops.Count < 1) | ||
{ | ||
throw new ArgumentException ("At least one color stop must be provided."); | ||
} | ||
|
||
_steps = steps.ToList (); | ||
|
||
// If multiple colors and only 1 step assume same distance applies to all steps | ||
if (_stops.Count > 2 && _steps.Count == 1) | ||
{ | ||
_steps = Enumerable.Repeat (_steps.Single (),_stops.Count() - 1).ToList(); | ||
} | ||
|
||
if (_steps.Any (step => step < 1)) | ||
{ | ||
throw new ArgumentException ("Steps must be greater than 0."); | ||
} | ||
|
||
if (_steps.Count != _stops.Count - 1) | ||
{ | ||
throw new ArgumentException ("Number of steps must be N-1"); | ||
} | ||
|
||
_loop = loop; | ||
Spectrum = GenerateGradient (_steps); | ||
} | ||
|
||
/// <summary> | ||
/// 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> | ||
/// <returns></returns> | ||
/// <exception cref="ArgumentOutOfRangeException"></exception> | ||
public Color GetColorAtFraction (double fraction) | ||
{ | ||
if (double.IsNaN (fraction)) | ||
{ | ||
return Spectrum.Last (); | ||
} | ||
|
||
if (fraction < 0 || fraction > 1) | ||
{ | ||
throw new ArgumentOutOfRangeException (nameof (fraction), "Fraction must be between 0 and 1."); | ||
} | ||
|
||
int index = (int)(fraction * (Spectrum.Count - 1)); | ||
return Spectrum [index]; | ||
} | ||
|
||
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; | ||
} | ||
|
||
var stopsToUse = _stops.ToList (); | ||
var stepsToUse = _steps.ToList (); | ||
|
||
if (_loop) | ||
{ | ||
stopsToUse.Add (_stops [0]); | ||
stepsToUse.Add (_steps.First ()); | ||
} | ||
|
||
var colorPairs = stopsToUse.Zip (stopsToUse.Skip (1), (start, end) => new { start, end }); | ||
var stepsList = stepsToUse; | ||
|
||
foreach (var (colorPair, thesteps) in colorPairs.Zip (stepsList, (pair, step) => (pair, step))) | ||
{ | ||
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 new Color (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> | ||
/// </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> (); | ||
|
||
switch (direction) | ||
{ | ||
case GradientDirection.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 Point (col, row)] = color; | ||
} | ||
} | ||
break; | ||
|
||
case GradientDirection.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 Point (col, row)] = color; | ||
} | ||
} | ||
break; | ||
|
||
case GradientDirection.Radial: | ||
for (int row = 0; row <= maxRow; row++) | ||
{ | ||
for (int col = 0; col <= maxColumn; col++) | ||
{ | ||
double distanceFromCenter = FindNormalizedDistanceFromCenter (maxRow, maxColumn, new Point (col, row)); | ||
Color color = GetColorAtFraction (distanceFromCenter); | ||
gradientMapping [new Point (col, row)] = color; | ||
} | ||
} | ||
break; | ||
|
||
case GradientDirection.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 Point (col, row)] = color; | ||
} | ||
} | ||
break; | ||
} | ||
|
||
return gradientMapping; | ||
} | ||
|
||
private 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; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
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; | ||
|
||
/// <summary> | ||
/// Creates a new instance of the <see cref="GradientFill"/> class that can return | ||
/// color for any point in the given <paramref name="area"/> using the provided | ||
/// <paramref name="gradient"/> and <paramref name="direction"/>. | ||
/// </summary> | ||
/// <param name="area"></param> | ||
/// <param name="gradient"></param> | ||
/// <param name="direction"></param> | ||
public GradientFill (Rectangle area, Gradient gradient, GradientDirection direction) | ||
{ | ||
_map = gradient.BuildCoordinateColorMapping (area.Height - 1, area.Width - 1, direction) | ||
.ToDictionary ( | ||
kvp => new Point (kvp.Key.X + area.X, kvp.Key.Y + area.Y), | ||
kvp => kvp.Value); | ||
} | ||
|
||
/// <summary> | ||
/// Returns the color to use for the given <paramref name="point"/> or Black if it | ||
/// lies outside of the prepared gradient area (see constructor). | ||
/// </summary> | ||
/// <param name="point"></param> | ||
/// <returns></returns> | ||
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 | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
| ||
namespace Terminal.Gui; | ||
|
||
/// <summary> | ||
/// Describes an area fill (e.g. solid color or gradient). | ||
/// </summary> | ||
public interface IFill | ||
{ | ||
/// <summary> | ||
/// Returns the color that should be used at the given point | ||
/// </summary> | ||
/// <param name="point"></param> | ||
/// <returns></returns> | ||
Color GetColor (Point point); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.