UI/Common/RingStroke.cs
using Sandbox.UI;
namespace sGBA;
internal readonly struct RingGradient
{
private readonly float[] _stops;
private readonly Color[] _colors;
public RingGradient( float[] stops, Color[] colors )
{
_stops = stops;
_colors = colors;
}
public Color Sample( float offset )
{
offset -= MathF.Floor( offset );
for ( int i = 0; i < _stops.Length - 1; i++ )
{
if ( offset < _stops[i] || offset > _stops[i + 1] ) continue;
float amount = (offset - _stops[i]) / (_stops[i + 1] - _stops[i]);
return Lerp( _colors[i], _colors[i + 1], amount );
}
return _colors[0];
}
private static Color Lerp( Color from, Color to, float amount )
{
amount = Math.Clamp( amount, 0f, 1f );
return new Color(
from.r + (to.r - from.r) * amount,
from.g + (to.g - from.g) * amount,
from.b + (to.b - from.b) * amount,
1f );
}
}
internal readonly struct RingStroke
{
private readonly RingGradient _gradient;
private readonly Vector2 _center;
private readonly float _phase;
private readonly float _halfDot;
private readonly float _sampleStep;
private readonly float _alpha;
public RingStroke( RingGradient gradient, Vector2 center, float phase, float halfDot, float sampleStep, float alpha = 1f )
{
_gradient = gradient;
_center = center;
_phase = phase;
_halfDot = halfDot;
_sampleStep = sampleStep;
_alpha = alpha;
}
public void Horizontal( float startX, float endX, float pointY )
{
float direction = MathF.Sign( endX - startX );
float distance = MathF.Abs( endX - startX );
int steps = Math.Max( 1, (int)MathF.Ceiling( distance / _sampleStep ) );
for ( int step = 0; step <= steps; step++ )
{
float pointX = startX + direction * MathF.Min( step * _sampleStep, distance );
Dot( new Vector2( pointX, pointY ) );
}
}
public void Vertical( float pointX, float startY, float endY )
{
float direction = MathF.Sign( endY - startY );
float distance = MathF.Abs( endY - startY );
int steps = Math.Max( 1, (int)MathF.Ceiling( distance / _sampleStep ) );
for ( int step = 0; step <= steps; step++ )
{
float pointY = startY + direction * MathF.Min( step * _sampleStep, distance );
Dot( new Vector2( pointX, pointY ) );
}
}
public void Corner( float centerX, float centerY, float radius, float startDegrees, float endDegrees )
{
float arcLength = MathF.Abs( endDegrees - startDegrees ) * MathF.PI / 180f * radius;
int steps = Math.Max( 1, (int)MathF.Ceiling( arcLength / _sampleStep ) );
for ( int step = 0; step <= steps; step++ )
{
float amount = step / (float)steps;
float degrees = startDegrees + (endDegrees - startDegrees) * amount;
float radians = degrees * MathF.PI / 180f;
Dot( new Vector2( centerX + MathF.Cos( radians ) * radius, centerY + MathF.Sin( radians ) * radius ) );
}
}
public void FullCircle( float radius )
{
float circumference = MathF.PI * 2f * radius;
int steps = Math.Max( 1, (int)MathF.Ceiling( circumference / _sampleStep ) );
for ( int step = 0; step <= steps; step++ )
{
float amount = step / (float)steps;
float radians = amount * MathF.PI * 2f;
var point = new Vector2( _center.x + MathF.Cos( radians ) * radius, _center.y + MathF.Sin( radians ) * radius );
Panel.Draw.Circle( point, _halfDot, WithAlpha( _gradient.Sample( amount + _phase ) ) );
}
}
private void Dot( Vector2 point )
{
Panel.Draw.Circle( point, _halfDot, ColorAt( point ) );
}
private Color ColorAt( Vector2 point )
{
float angle = MathF.Atan2( point.y - _center.y, point.x - _center.x );
float offset = (angle + MathF.PI) / (MathF.PI * 2f);
return WithAlpha( _gradient.Sample( offset + _phase ) );
}
private Color WithAlpha( Color color )
{
return new Color( color.r, color.g, color.b, color.a * _alpha );
}
}